#include "hashdat.h"

template <int n, int b>
HashDat<n, b>::HashDat()
{
    m_fileName = "";
    m_hashFile = NULL;
    m_bucket = NULL;
    m_radix = NULL;
}

template <int n, int b>
HashDat<n, b>::HashDat(const char *fileName)
{
    m_fileName = fileName;
    m_hashFile = NULL;
    m_bucket = NULL;
    m_radix = NULL;
    openHashFile();
    buildRadix();
}

template <int n, int b>
void HashDat<n, b>::insert(Article * article)
{
    int number = article->getNumber();

    Article * checkArticle = &(retrieve(number));
    if(checkArticle->getNumber()!= -1)
    {
        cout << "Artikel schon vorhanden (Nummer: " << number << " )" << endl << endl;
        return;
    }

    int position = m_radix->retrieve(number);

    if (position == -1)
    {
        int depth = m_radix->getDepth(number);
        Bucket<n> * bucket = new Bucket<n>();

        bucket->addArticle(article);

        position = bucketCount();
        bucket->setHashDigitCount(depth + 1);
        bucket->write(position, m_hashFile);
        m_hashFile->flush();
        m_radix->insert(number % b, depth + 1, position);
        return;
    }

    Bucket<n> * bucket = new Bucket<n>();
    bucket->read(position, m_hashFile);

    if(bucket->addArticle(article))
    {
        bucket->write(position,m_hashFile);
        m_hashFile->flush();
    }
    else
    {
        int digits = bucket->getHashDigitCount();
        unsigned int mask = 1;
        mask = mask << digits;
        Bucket<n> *zeroBucket = NULL;
        Bucket<n> *oneBucket = NULL;

        unsigned int hash;

        for(int i = 0; i < n; i++)
        {
            Article * oldArticle = bucket->getArticle(i);
            number = oldArticle->getNumber();
            hash = number % b;

            unsigned int bit = (hash & mask) >> digits;

            if (bit == 0)
            {
                if (zeroBucket == NULL)
                {
                    zeroBucket = new Bucket<n>();
                    zeroBucket->setHashDigitCount(digits + 1);
                }

                zeroBucket->addArticle(oldArticle);
            }
            else if(bit == 1)
            {
                if (oneBucket == NULL)
                {
                    oneBucket = new Bucket<n>();
                    oneBucket->setHashDigitCount(digits + 1);
                }

                oneBucket->addArticle(oldArticle);
            }
        }

        mask = 1;

        for (int i = 1; i <= digits; ++i)
            mask = mask + (1 << i);

        bool bucketOverwritten = false;

        if (zeroBucket != NULL)
        {
            hash = ((zeroBucket->getArticle(0))->getNumber()) % b;
            zeroBucket->write(position, m_hashFile);
            bucketOverwritten = true;

            int bits = hash & mask;
            m_radix->insert(bits, digits + 1, position);
        }

        if (oneBucket != NULL)
        {
            hash = ((oneBucket->getArticle(0))->getNumber()) % b;

            if(bucketOverwritten)
                position = bucketCount();

            oneBucket->write(position ,m_hashFile);

            int bits = hash & mask;
            m_radix->insert(bits, digits + 1, position);
        }

        insert(article);

        delete zeroBucket;
        delete oneBucket;
    }

    cout <<"Artikel eingefuegt" << endl << endl;

    delete bucket;
}

template <int n, int b>
Article HashDat<n, b>::retrieve(int number)
{
    Article article;
    int position = m_radix->retrieve(number);

    if(position == -1)
        return article;

    Bucket<n> * bucket = new Bucket<n>();
    bucket->read(position, m_hashFile);

    for(int i = 0; i < n; i++)
    {	
        if((bucket->getArticle(i))->getNumber() == number)
        {
            article = *(bucket->getArticle(i));
        }
    }

    delete bucket;
    return article;
}

template <int n, int b>
void HashDat<n, b>::display()
{
    long count = bucketCount();
    cout << "Vorhandene Buckets und Artikel (" << count << " Buckets)" << endl;

    for(int i = 0; i < count; i++)
    {
        Bucket<n> * bucket = new Bucket<n>();
        bucket->read(i, m_hashFile);
        cout << (*bucket);
        delete bucket;
    }
}

template <int n, int b>
void HashDat<n, b>::openHashFile()
{
    m_hashFile = new fstream();
    m_hashFile->open(m_fileName, ios::in | ios::out | ios::binary);

    if(!(m_hashFile->is_open()))
    {
        delete m_hashFile;
        m_hashFile = new fstream(m_fileName, ios::in | ios::out | ios::binary | ios::trunc);

        if(!m_hashFile->is_open())
        {
            cout << "Datei " << m_fileName << " kann nicht angelegt werden" << endl;
            return;
        }

        Bucket<n> * bucket = new Bucket<n>();
        bucket->setHashDigitCount(0);
        bucket->write(0, m_hashFile);

        cout << "Datei " << m_fileName << " angelegt" << endl;
    }
    else
        cout << "Datei " << m_fileName << " existiert" << endl;
}

template <int n, int b>
void HashDat<n, b>::buildRadix()
{
    delete m_radix;
    m_radix = new Radix(b);
    long count = bucketCount();
    cout << "Datei hat " << count << " Buckets" << endl;

    for(int i = 0; i < count; i++)
    {
        Bucket<n> * bucket = new Bucket<n>();
        bucket->read(i, m_hashFile);

        Article * article = bucket->getArticle(0);

        unsigned int hash = (article->getNumber()) % b;
        m_radix->insert(hash, bucket->getHashDigitCount(), i);

        delete bucket;
    }
}

template <int n, int b>
long HashDat<n, b>::bucketCount()
{
    m_hashFile->seekg(0L, ios::end);
    long count = m_hashFile->tellg();
    count = count / Bucket<n>::sizeBucket;
    return count;
}
